/**
 *******************************************************************************
 * @file  flash.c
 * @brief This file provides firmware functions to manage the Flash driver.
 @verbatim
   Change Logs:
   Date             Author          Notes
   2019-06-26       Yangjp          First version
 @endverbatim
 *******************************************************************************
 * Copyright (C) 2016, Huada Semiconductor Co., Ltd. All rights reserved.
 *
 * This software is owned and published by:
 * Huada Semiconductor Co., Ltd. ("HDSC").
 *
 * BY DOWNLOADING, INSTALLING OR USING THIS SOFTWARE, YOU AGREE TO BE BOUND
 * BY ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT.
 *
 * This software contains source code for use with HDSC
 * components. This software is licensed by HDSC to be adapted only
 * for use in systems utilizing HDSC components. HDSC shall not be
 * responsible for misuse or illegal use of this software for devices not
 * supported herein. HDSC is providing this software "AS IS" and will
 * not be responsible for issues arising from incorrect user implementation
 * of the software.
 *
 * Disclaimer:
 * HDSC MAKES NO WARRANTY, EXPRESS OR IMPLIED, ARISING BY LAW OR OTHERWISE,
 * REGARDING THE SOFTWARE (INCLUDING ANY ACCOMPANYING WRITTEN MATERIALS),
 * ITS PERFORMANCE OR SUITABILITY FOR YOUR INTENDED USE, INCLUDING,
 * WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, THE IMPLIED
 * WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE OR USE, AND THE IMPLIED
 * WARRANTY OF NONINFRINGEMENT.
 * HDSC SHALL HAVE NO LIABILITY (WHETHER IN CONTRACT, WARRANTY, TORT,
 * NEGLIGENCE OR OTHERWISE) FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT
 * LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION,
 * LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING FROM USE OR
 * INABILITY TO USE THE SOFTWARE, INCLUDING, WITHOUT LIMITATION, ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOSS OF DATA,
 * SAVINGS OR PROFITS,
 * EVEN IF Disclaimer HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * YOU ASSUME ALL RESPONSIBILITIES FOR SELECTION OF THE SOFTWARE TO ACHIEVE YOUR
 * INTENDED RESULTS, AND FOR THE INSTALLATION OF, USE OF, AND RESULTS OBTAINED
 * FROM, THE SOFTWARE.
 *
 * This software may be replicated in part or whole for the licensed use,
 * with the restriction that this Disclaimer and Copyright notice must be
 * included with each copy of this software, whether used in part or whole,
 * at all times.
 *******************************************************************************
 */

/*******************************************************************************
 * Include files
 ******************************************************************************/
#include "flash.h"
#include "basic.h"

/**
 * @addtogroup HC32_Boot
 * @{
 */

/**
 * @defgroup FLASH FLASH
 * @{
 */

/*******************************************************************************
 * Local type definitions ('typedef')
 ******************************************************************************/

/*******************************************************************************
 * Local pre-processor symbols/macros ('#define')
 ******************************************************************************/
/**
 * @defgroup Flash_Local_Macros Flash Local Macros
 * @{
 */

/* EFM register bit definition */
#if defined(HC32F460)
    #define EFM_FSCLR_PEWERRCLR         (0x00000001UL)
    #define EFM_FSCLR_PEPRTERRCLR       (0x00000002UL)
    #define EFM_FSCLR_PGSZERRCLR        (0x00000004UL)
    #define EFM_FSCLR_PGMISMTCHCLR      (0x00000008UL)
    #define EFM_FSCLR_OPTENDCLR         (0x00000010UL)
    #define EFM_FSCLR_RDCOLERRCLR       (0x00000020UL)

    #define EFM_FWMC_PEMODE             (0x00000001UL)
    #define EFM_FWMC_PEMOD_POS          (4U)
    #define EFM_FWMC_PEMOD              (0x00000070UL)

    #define EFM_FSR_PGMISMTCH           (0x00000008UL)
    #define EFM_FSR_OPTEND              (0x00000010UL)
    #define EFM_FSR_RDCOLERR            (0x00000020UL)
    #define EFM_FSR_RDY                 (0x00000100UL)
#endif

#if defined(HC32F460)
    #define EFM_TIMEOUT                 (0x20000UL)

    #define ERROR_FLAG_CLEAR_MASK       (EFM_FSCLR_PEWERRCLR  | EFM_FSCLR_PEPRTERRCLR  | \
                                         EFM_FSCLR_PGSZERRCLR | EFM_FSCLR_PGMISMTCHCLR | \
                                         EFM_FSCLR_OPTENDCLR  | EFM_FSCLR_RDCOLERRCLR)
#elif defined(HC32F120) || defined(HC32M120)
    #define EFM_TIMEOUT                 (0x10000UL)

    #define ERROR_FLAG_CLEAR_MASK       (EFM_FSCLR_PEWERRCLR  | EFM_FSCLR_PEPRTERRCLR  | \
                                         EFM_FSCLR_OPTENDCLR  | EFM_FSCLR_RDCOLERRCLR  | \
                                         EFM_FSCLR_PGMISMTCHCLR)
#elif defined(HC32F160)
    #define EFM_TIMEOUT                 (0x10000UL)

    #define ERROR_FLAG_CLEAR_MASK       (EFM_FSCLR_PEWERRCLR | EFM_FSCLR_PEPRTERRCLR  | \
                                         EFM_FSCLR_OPTENDCLR | EFM_FSCLR_PGMISMTCHCLR | \
                                         EFM_FSCLR_COLERRCLR | EFM_FSCLR_ERCALCLR)
#elif defined(HC32F4A0)
    #define EFM_ADDR_SECTOR128          (0x00100000UL)  /*!< Sector 128 */
    #define EFM_OTP_BLOCK16             (0x03000000UL)  /*!< OTP block16 2K Bytes */

    #define EFM_TIMEOUT                 (0x30000UL)

    #define ERROR_FLAG_CLEAR_MASK       (EFM_FSCLR_OTPWERRCLR0 | EFM_FSCLR_PRTWERRCLR0 | EFM_FSCLR_PGSZERRCLR0 | \
                                         EFM_FSCLR_MISMTCHCLR0 | EFM_FSCLR_OPTENDCLR0  | EFM_FSCLR_COLERRCLR0  | \
                                         EFM_FSCLR_PRTWERRCLR1 | EFM_FSCLR_PGSZERRCLR1 | EFM_FSCLR_MISMTCHCLR1 | \
                                         EFM_FSCLR_OPTENDCLR1  | EFM_FSCLR_COLERRCLR1)
#elif defined(HC32M423)
    #define EFM_TIMEOUT                 (0x20000UL)

    #define ERROR_FLAG_CLEAR_MASK       (EFM_FSCLR_PEWERRCLR    | EFM_FSCLR_PEPRTERRCLR | \
                                         EFM_FSCLR_PGMISMTCHCLR | EFM_FSCLR_OPTENDCLR   | \
                                         EFM_FSCLR_RDCOLERRCLR)
#endif

/**
 * @}
 */

/*******************************************************************************
 * Global variable definitions (declared in header file with 'extern')
 ******************************************************************************/

/*******************************************************************************
 * Local function prototypes ('static')
 ******************************************************************************/

/*******************************************************************************
 * Local variable definitions ('static')
 ******************************************************************************/

/*******************************************************************************
 * Function implementation - global ('extern') and local ('static')
 ******************************************************************************/
/**
 * @defgroup Flash_Global_Functions Flash Global Functions
 * @{
 */

/**
 * @brief  Erase flash sector.
 * @param  u32Addr                      Flash address
 * @retval An en_result_t enumeration value:
 *           - Ok: Erase succeeded
 *           - ErrorInvalidParameter: 'u32Addr' out of valid range
 *           - ErrorTimeout: Erase timeout
 */
en_result_t EFM_EraseSector(uint32_t u32Addr)
{
    en_result_t enRet;
    uint32_t u32EfmFlag;
    uint32_t u32EfmClrFlag;

    if (u32Addr >= (FLASH_BASE + FLASH_SIZE))
    {
        return ErrorInvalidParameter;
    }

    if (0UL != (u32Addr % 4U))
    {
        u32Addr = (u32Addr / FLASH_SECTOR_SIZE) * FLASH_SECTOR_SIZE;
    }

    /* CLear the error flag */
    WRITE_REG(EFM_BASE->FSCLR, ERROR_FLAG_CLEAR_MASK);
#if !defined(HC32F4A0)
    /* Enable flash erase and program */
    SET_BIT(EFM_BASE->FWMC, EFM_FWMC_PEMODE);
#endif
    /* Set sector erase mode */
    MODIFY_REG(EFM_BASE->FWMC, EFM_FWMC_PEMOD, (0x04UL << EFM_FWMC_PEMOD_POS));
    *(__IO uint32_t *)(u32Addr) = 0UL;
#if defined(HC32F4A0)
    if((u32Addr < EFM_ADDR_SECTOR128) || (u32Addr >= EFM_OTP_BLOCK16))
    {
        u32EfmFlag    = EFM_FSR_RDY0;
        u32EfmClrFlag = EFM_FSCLR_OPTENDCLR0;
    }
    else
    {
        u32EfmFlag    = EFM_FSR_RDY1;
        u32EfmClrFlag = EFM_FSCLR_OPTENDCLR1;
    }
#else
    u32EfmFlag    = EFM_FSR_RDY;
    u32EfmClrFlag = EFM_FSCLR_OPTENDCLR;
#endif
    enRet = EFM_WaitForOperationDone(u32EfmFlag, EFM_TIMEOUT);
    /* Clear the end of operate flag */
    SET_BIT(EFM_BASE->FSCLR, u32EfmClrFlag);
    /* Set read only mode */
    MODIFY_REG(EFM_BASE->FWMC, EFM_FWMC_PEMOD, 0UL);
#if !defined(HC32F4A0)
    /* Disable flash erase and program */
    CLEAR_BIT(EFM_BASE->FWMC, EFM_FWMC_PEMODE);
#endif

    return enRet;
}

/**
 * @brief  Write data to flash.
 * @param  u32Addr                      Flash address
 * @param  pu8WriteBuff                 Pointer to the buffer to be written
 * @param  u32ByteLength                Buffer length
 * @retval An en_result_t enumeration value:
 *           - Ok: Write data succeeded
 *           - ErrorInvalidParameter: The parameters is invalid
 *           - ErrorTimeout: Write data timeout
 *           - ErrorOperationInProgress: Operation error
 *           - ErrorAddressAlignment: Address alignment error
 */
en_result_t EFM_WriteFlash(uint32_t u32Addr, const uint8_t *pu8WriteBuff, uint32_t u32ByteLength)
{
    en_result_t enRet = Ok;
    uint32_t i;
    uint32_t u32LastWord = 0UL;
    uint32_t u32WordLength, u8ByteRemain;
    const uint32_t *pu32WriteBuff;
    __IO uint32_t *pu32FlashAddr;
    uint32_t u32EfmFlag;
    uint32_t u32EfmClrFlag;
    uint32_t u32EfmFlag1;
    uint32_t u32EfmClrFlag1;

    if (((u32Addr + u32ByteLength) > (FLASH_BASE + FLASH_SIZE)) ||
        (NULL == pu8WriteBuff) || (0UL == u32ByteLength))
    {
        return ErrorInvalidParameter;
    }

    if (0UL != (u32Addr % 4U))
    {
        return ErrorAddressAlignment;
    }

    pu32FlashAddr = (__IO uint32_t *)u32Addr;
    pu32WriteBuff = (const uint32_t *)(uint32_t)pu8WriteBuff;
    u32WordLength = u32ByteLength / 4U;
    u8ByteRemain  = u32ByteLength % 4U;
    if (0U != u8ByteRemain)
    {
        u32LastWord = pu32WriteBuff[u32WordLength];
        u32LastWord |= (0xFFFFFFFFUL << (u8ByteRemain * 8U));
        u32WordLength++;
    }

    /* CLear the error flag */
    WRITE_REG(EFM_BASE->FSCLR, ERROR_FLAG_CLEAR_MASK);
#if !defined(HC32F4A0)
    /* Enable flash erase and program */
    SET_BIT(EFM_BASE->FWMC, EFM_FWMC_PEMODE);
#endif
    /* Set Program and read back mode */
    MODIFY_REG(EFM_BASE->FWMC, EFM_FWMC_PEMOD, (0x02UL << EFM_FWMC_PEMOD_POS));
    /* program data */
    for (i = 0UL; i < u32WordLength; i++)
    {
        if ((i == (u32WordLength - 1U)) && (0U != u8ByteRemain))
        {
            *pu32FlashAddr = u32LastWord;
        }
        else
        {
            *pu32FlashAddr = pu32WriteBuff[i];
        }
    #if defined(HC32F4A0)
        if(((uint32_t)pu32FlashAddr < EFM_ADDR_SECTOR128) || ((uint32_t)pu32FlashAddr >= EFM_OTP_BLOCK16))
        {
            u32EfmFlag     = EFM_FSR_RDY0;
            u32EfmClrFlag  = EFM_FSCLR_OPTENDCLR0;
            u32EfmFlag1    = EFM_FSR_MISMTCH0;
            u32EfmClrFlag1 = EFM_FSCLR_MISMTCHCLR0;
        }
        else
        {
            u32EfmFlag     = EFM_FSR_RDY1;
            u32EfmClrFlag  = EFM_FSCLR_OPTENDCLR1;
            u32EfmFlag1    = EFM_FSR_MISMTCH1;
            u32EfmClrFlag1 = EFM_FSCLR_MISMTCHCLR1;
        }
    #else
        u32EfmFlag     = EFM_FSR_RDY;
        u32EfmClrFlag  = EFM_FSCLR_OPTENDCLR;
        u32EfmFlag1    = EFM_FSR_PGMISMTCH;
        u32EfmClrFlag1 = EFM_FSCLR_PGMISMTCHCLR;
    #endif
        if (Ok != EFM_WaitForOperationDone(u32EfmFlag, EFM_TIMEOUT))
        {
            enRet = ErrorTimeout;
            break;
        }
        if (u32EfmFlag1 == READ_BIT(EFM_BASE->FSR, u32EfmFlag1))
        {
            /* Clear flag */
            SET_BIT(EFM_BASE->FSCLR, u32EfmClrFlag1);
            enRet = ErrorOperationInProgress;
            break;
        }
        pu32FlashAddr++;
        /* CLear the end of operate flag */
        SET_BIT(EFM_BASE->FSCLR, u32EfmClrFlag);
    }
    /* Set read only mode */
    MODIFY_REG(EFM_BASE->FWMC, EFM_FWMC_PEMOD, 0UL);
#if !defined(HC32F4A0)
    /* Disable flash erase and program */
    CLEAR_BIT(EFM_BASE->FWMC, EFM_FWMC_PEMODE);
#endif

    return enRet;
}

/**
 * @brief  Read data from flash.
 * @param  u32Addr                      Flash address
 * @param  pu8ReadBuff                  Pointer to the buffer to be reading
 * @param  u32ByteLength                Buffer length
 * @retval An en_result_t enumeration value:
 *           - Ok: Read data succeeded
 *           - ErrorInvalidParameter: The parameters is invalid
 *           - ErrorTimeout: Read data timeout
 *           - ErrorOperationInProgress: Operation error
 *           - ErrorAddressAlignment: Address alignment error
 */
en_result_t EFM_ReadFlash(uint32_t u32Addr, uint8_t *pu8ReadBuff, uint32_t u32ByteLength)
{
    uint32_t i;
    uint32_t u32WordLength, u8ByteRemain;
    uint32_t *pu32ReadBuff;
    uint8_t  *pu8Buff;
    __IO uint32_t *pu32FlashAddr;
    __IO uint8_t  *pu8FlashAddr;

    if (((u32Addr + u32ByteLength) > (FLASH_BASE + FLASH_SIZE)) || 
        (NULL == pu8ReadBuff) || (0UL == u32ByteLength))
    {
        return ErrorInvalidParameter;
    }

    if (0UL != (u32Addr % 4U))
    {
        return ErrorAddressAlignment;
    }

    pu32FlashAddr = (uint32_t *)u32Addr;
    pu32ReadBuff  = (uint32_t *)(uint32_t)pu8ReadBuff;
    u32WordLength = u32ByteLength / 4U;
    u8ByteRemain  = u32ByteLength % 4U;
    /* Read data */
    for (i = 0UL; i < u32WordLength; i++)
    {
        *(pu32ReadBuff++) = *(pu32FlashAddr++);
    }
    if (0UL != u8ByteRemain)
    {
        pu8Buff      = (uint8_t *)pu32ReadBuff;
        pu8FlashAddr = (__IO uint8_t *)pu32FlashAddr;
        for (i = 0UL; i < u8ByteRemain; i++)
        {
            *(pu8Buff++) = *(pu8FlashAddr++);
        }
    }

    return Ok;
}

/**
 * @brief  Wait for a EFM operation to complete.
 * @param  u32Flag                      Waitting the flag type
 * @param  u32Timeout                   Maximum EFM operation timeout
 * @retval An en_result_t enumeration value:
 *           - Ok: Operation succeeded
 *           - ErrorTimeout: Operation timeout
 */
en_result_t EFM_WaitForOperationDone(uint32_t u32Flag, uint32_t u32Timeout)
{
    __IO uint32_t u32Delay = 0UL;

    /* Wait for the EFM operation to complete by polling on BUSY flag to be
       reset. Even if the EFM operation fails, the BUSY flag will be reset and
       an error flag will be set */
    while (u32Flag != READ_BIT(EFM_BASE->FSR, u32Flag))
    {
        u32Delay++;
        if (u32Delay >= u32Timeout)
        {
            return ErrorTimeout;
        }
    }

    return Ok;
}

/**
 * @}
 */

/**
 * @}
 */

/**
* @}
*/

/******************************************************************************
 * EOF (not truncated)
 *****************************************************************************/
